[Node.js] [AWS SDK v3] StreamでCSVファイルをS3からS3にコピーする
広島の吉川です。
Node.js Streamに入門してみた | DevelopersIO
さっき書いたこちらの記事の発展として、S3を使ったStream処理をやってみたいと思います。
ちょっとこちらの真似をして、「S3から別のS3にCSVをコピーする」というスクリプトを書いてみたいと思います。大きく違うのはAWS SDKのバージョンで、参考元記事はv2で、本記事はv3でやっていきます。
環境
- node 16.14.0
- typescript 4.6.3
- @aws-sdk/{client-s3,lib-storage} 3.58.0
- csv 6.0.5
S3のWriteStream
まずS3にCSVをアップロードする処理を行っていきます。
v2だとS3クライアントから .upload()
というメソッドが生えていたのですが、v3には同じものがなさそうでした。
node.js - How to upload a stream to S3 with AWS SDK v3 - Stack Overflow
こちらのStackOverflowによると、 @aws-sdk/client-s3
の他に @aws-sdk/lib-storage
パッケージを組み合わせることで同等処理の実現ができるようです。そのように書いていきます。
import { S3Client } from '@aws-sdk/client-s3' import { Upload } from '@aws-sdk/lib-storage' import { generate } from 'csv' const region = 'ap-northeast-1' const s3Client = new S3Client({ region, }) const bucketName = 'YOUR_BUCKET_NAME' const keyName = 'example.csv' const main = async () => { const csvStream = generate({ length: 5000000 }) const upload = new Upload({ client: s3Client, params: { Bucket: bucketName, Key: keyName, Body: csvStream, }, }) upload.on('httpUploadProgress', (progress) => { console.log(progress) }) await upload.done() } main()
Body
引数に stream.Readable
を渡せるようになっているので、そのまま使わせてもらう形です。
S3のReadStream
AWS SDK v3ではなんと最初から GetObjectOutput.Body
が stream.Readable
になっています。なので、普通に扱えばReadStreamでの読み取りになります。
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3' import fs from 'fs' import { Readable } from 'stream' const region = 'ap-northeast-1' const s3Client = new S3Client({ region, }) const bucketName = 'YOUR_BUCKET_NAME' const keyName = 'example.csv' const main = async () => { const output = await s3Client.send( new GetObjectCommand({ Bucket: bucketName, Key: keyName, }) ) ;(output.Body as Readable).pipe(fs.createWriteStream('example.csv')) } main()
ただ、 Body
を stream.Readable
で返す仕様が逆に利用者を混乱させている面もあるようで、下記のようなIssueが建っていました。
S3.GetObject no longer returns the result as a string · Issue #1877 · aws/aws-sdk-js-v3
たしかにそれほど大きくないテキストファイルなら最初から string
型などで取得できた方が楽な場合も多そうです。上のIssueによると string
に変換したい場合はget-streamというライブラリを使うのが簡単そうです。
Streamを使ってS3から別のS3へCSVをコピーする
では、あるS3バケットのCSVファイルを別のS3バケットにコピーするという処理を書いていきます。
今回は362MBのCSVファイルをS3バケット1に入れておきました。
これをS3バケット2にコピーするというシナリオでコードを書きます。
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3' import { Upload } from '@aws-sdk/lib-storage' const region = 'ap-northeast-1' const s3Client = new S3Client({ region, }) const bucketName1 = 'YOUR_BUCKET_NAME_1' const keyName1 = 'example.csv' const bucketName2 = 'YOUR_BUCKET_NAME_2' const keyName2 = 'copied-example.csv' const main = async () => { const getObjectOutput = await s3Client.send( new GetObjectCommand({ Bucket: bucketName1, Key: keyName1, }) ) const upload = new Upload({ client: s3Client, params: { Bucket: bucketName2, Key: keyName2, Body: getObjectOutput.Body, }, }) upload.on('httpUploadProgress', (progress) => { console.log(progress) }) await upload.done() } main()
前回と同じ方法で計測したところメモリ使用量は約244.7MBでした。
Streamを使わずS3から別のS3へCSVをコピーする
比較のためにあえてStreamでデータを流すことを避けたコードも書きました。それが下記です。
import { GetObjectCommand, PutObjectCommand, S3Client, } from '@aws-sdk/client-s3' import getStream from 'get-stream' import { Readable } from 'stream' const region = 'ap-northeast-1' const s3Client = new S3Client({ region, }) const bucketName1 = 'YOUR_BUCKET_NAME_1' const keyName1 = 'example.csv' const bucketName2 = 'YOUR_BUCKET_NAME_2' const keyName2 = 'copied-example.csv' const main = async () => { const getObjectOutput = await s3Client.send( new GetObjectCommand({ Bucket: bucketName1, Key: keyName1, }) ) const csvString = await getStream(getObjectOutput.Body as Readable) await s3Client.send( new PutObjectCommand({ Bucket: bucketName2, Key: keyName2, Body: csvString, }) ) } main()
さきほど紹介したget-streamを使って stream.Readable
を string
に変換した後、アップロードするようにしてみました。
こちらも計測したところメモリ使用量は約1568.5MBでした。
まとめ
ケース | メモリ使用量 |
---|---|
Streamを使ってS3から別のS3へCSVをコピーする | 244.7MB |
Streamを使わずS3から別のS3へCSVをコピーする | 1568.5MB |
こちらの例でもStreamを使うことでメモリ使用量を抑えることができました。
参考
- 【S3からS3へ】Node.js の Streaming API を使って Lambda Function のみで CSVファイルを JSON Lines ファイルへ変換する | DevelopersIO
- node.js - How to upload a stream to S3 with AWS SDK v3 - Stack Overflow
- amazon web services - AWS s3 V3 Javascript SDK stream file from bucket (GetObjectCommand) - Stack Overflow
- S3.GetObject no longer returns the result as a string · Issue #1877 · aws/aws-sdk-js-v3
- Node.jsの使用メモリを観測する方法 - Qiita
- airbnb/node-memwatch: A NodeJS library to keep an eye on your memory usage, and discover and isolate leaks.
- fujiwara/lambroll: lambroll is a minimal deployment tool for AWS Lambda.
- esbuild - An extremely fast JavaScript bundler
- command - How to get the memory usage of a OS X/macOS process - Stack Overflow